from collections.abc import Iterable, Iterator, Mapping, Sequence
from functools import cache
from typing import Callable, TypeVar, overload

K = TypeVar('K')
V = TypeVar('V')


class LazyIterable(Iterable[V]):
    def __init__(self, iterable: Iterable[Callable[[], V]]):
        self._iterable = iterable

    def __iter__(self) -> Iterator[V]:
        for func in self._iterable:
            yield func()


class LazySequence(Sequence[V]):
    def __init__(self, sequence: Sequence[Callable[[], V]]):
        self._sequence = sequence

    def __len__(self):
        return len(self._sequence)

    @overload
    def __getitem__(self, index: int) -> V:
        ...

    @overload
    def __getitem__(self, index: slice) -> Sequence[V]:
        ...

    def __getitem__(self, item: int | slice) -> V | Sequence[V]:
        match item:
            case i if isinstance(i, int):
                return self._sequence[i]()
            case s if isinstance(s, slice):
                return LazySequence(self._sequence[s])
            case _:
                raise TypeError(f"Unexpected subscription {item}")


class LazyMapping(Mapping[K, V]):
    def __init__(self, mapping: Mapping[K, Callable[[], V]] | Iterable[tuple[K, Callable[[], V]]]):
        self._mapping = mapping if isinstance(mapping, Mapping) else dict(mapping)
        self._cached_getitem = cache(lambda k: self._mapping[k]())

    def __getitem__(self, key: K) -> V:
        return self._cached_getitem(key)

    def __len__(self) -> int:
        return len(self._mapping)

    def __iter__(self) -> Iterator[K]:
        return iter(self._mapping)

    def keys(self) -> Iterable[K]:
        return self._mapping.keys()

    def values(self) -> LazyIterable[V]:
        return LazyIterable(self._mapping.values())

    def items(self) -> LazyIterable[tuple[K, V]]:
        item_getter_factory = lambda key: lambda: (key, self[key]())
        items_getter = tuple(item_getter_factory(key) for key in self.keys())
        return LazyIterable(items_getter)
